/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "core_main.h"


#define BLOCK_ID					0x1D4A11

typedef struct memoryBlock_s {
	struct memoryBlock_s *	prev;
	struct memoryBlock_s *	next;

	memoryTag_t				tag;				// Tag identifier
	int						size;				// Requested size
	int						bytes;				// Bytes allocated, including header, guard, and padding
	int						id;					// Should be BLOCK_ID
} memoryBlock_t;

typedef struct {
	memoryBlock_t			blockChain;

	int						totalBlocks;
	int						totalBytes;

	int						frameAllocBlocks;
	int						frameAllocBytes;
	int						frameFreeBlocks;
	int						frameFreeBytes;
} memory_t;

static memory_t				mem;

static cvar_t *				mem_showMemoryUsage;


/*
 ==================
 Mem_Alloc
 ==================
*/
void *Mem_Alloc (int size, memoryTag_t tag){

	memoryBlock_t	*block;
	int				bytes;

	if (size < 0)
		Com_Error(true, "Mem_Alloc: size < 0");

	// Account for header and guard, and align to 8 byte boundary
	bytes = ALIGN(size + sizeof(memoryBlock_t) + sizeof(int), 8);

	// Allocate
	block = (memoryBlock_t *)malloc(bytes);
	if (!block)
		Com_Error(true, "Mem_Alloc: failed on allocation of %i bytes", bytes);

	// Fill in the block header
	block->tag = tag;
	block->size = size;
	block->bytes = bytes;
	block->id = BLOCK_ID;

	// Set a guard for memory trash testing
	*(int *)((byte *)block + sizeof(memoryBlock_t) + size) = BLOCK_ID;

	// Link it in the block chain
	block->prev = &mem.blockChain;
	block->next = mem.blockChain.next;

	mem.blockChain.next->prev = block;
	mem.blockChain.next = block;

	// Update counters
	mem.totalBlocks++;
	mem.totalBytes += bytes;

	mem.frameAllocBlocks++;
	mem.frameAllocBytes += bytes;

	return (void *)((byte *)block + sizeof(memoryBlock_t));
}

/*
 ==================
 Mem_ClearedAlloc
 ==================
*/
void *Mem_ClearedAlloc (int size, memoryTag_t tag){

	memoryBlock_t	*block;
	int				bytes;

	if (size < 0)
		Com_Error(true, "Mem_ClearedAlloc: size < 0");

	// Account for header and guard, and align to 8 byte boundary
	bytes = ALIGN(size + sizeof(memoryBlock_t) + sizeof(int), 8);

	// Allocate
	block = (memoryBlock_t *)calloc(bytes, 1);
	if (!block)
		Com_Error(true, "Mem_ClearedAlloc: failed on allocation of %i bytes", bytes);

	// Fill in the block header
	block->tag = tag;
	block->size = size;
	block->bytes = bytes;
	block->id = BLOCK_ID;

	// Set a guard for memory trash testing
	*(int *)((byte *)block + sizeof(memoryBlock_t) + size) = BLOCK_ID;

	// Link it in the block chain
	block->prev = &mem.blockChain;
	block->next = mem.blockChain.next;

	mem.blockChain.next->prev = block;
	mem.blockChain.next = block;

	// Update counters
	mem.totalBlocks++;
	mem.totalBytes += bytes;

	mem.frameAllocBlocks++;
	mem.frameAllocBytes += bytes;

	return (void *)((byte *)block + sizeof(memoryBlock_t));
}

/*
 ==================
 Mem_Free
 ==================
*/
void Mem_Free (const void *ptr){

	memoryBlock_t	*block;

	if (!ptr)
		Com_Error(true, "Mem_Free: NULL pointer");

	block = (memoryBlock_t *)((byte *)ptr - sizeof(memoryBlock_t));

	if (block->id != BLOCK_ID)
		Com_Error(true, "Mem_Free: memory block has invalid id");

	if (*(int *)((byte *)block + sizeof(memoryBlock_t) + block->size) != BLOCK_ID)
		Com_Error(true, "Mem_Free: memory block wrote past end");

	// Unlink it from the block chain
	block->prev->next = block->next;
	block->next->prev = block->prev;

	// Update counters
	mem.totalBlocks--;
	mem.totalBytes -= block->bytes;

	mem.frameFreeBlocks++;
	mem.frameFreeBytes += block->bytes;

	// Free
	free(block);
}

/*
 ==================
 Mem_TagFree
 ==================
*/
void Mem_TagFree (memoryTag_t tag){

	memoryBlock_t	*block, *next;

	for (block = mem.blockChain.next; block != &mem.blockChain; block = next){
		next = block->next;

		if (block->tag == tag)
			Mem_Free((void *)((byte *)block + sizeof(memoryBlock_t)));
	}
}

/*
 ==================
 Mem_Size
 ==================
*/
int Mem_Size (const void *ptr){

	memoryBlock_t	*block;

	if (!ptr)
		Com_Error(true, "Mem_Size: NULL pointer");

	block = (memoryBlock_t *)((byte *)ptr - sizeof(memoryBlock_t));

	if (block->id != BLOCK_ID)
		Com_Error(true, "Mem_Size: memory block has invalid id");

	if (*(int *)((byte *)block + sizeof(memoryBlock_t) + block->size) != BLOCK_ID)
		Com_Error(true, "Mem_Size: memory block wrote past end");

	return block->size;
}

/*
 ==================
 Mem_TagSize
 ==================
*/
int Mem_TagSize (memoryTag_t tag){

	memoryBlock_t	*block;
	int				totalSize = 0;

	for (block = mem.blockChain.next; block != &mem.blockChain; block = block->next){
		if (block->tag == tag)
			totalSize += block->size;
	}

	return totalSize;
}

/*
 ==================
 Mem_CopyString
 ==================
*/
char *Mem_CopyString (const char *string, memoryTag_t tag){

	char	*buffer;
	int		len;

	if (!string)
		Com_Error(true, "Mem_CopyString: NULL string");

	len = Str_Length(string);
	buffer = (char *)Mem_Alloc(len + 1, tag);

	memcpy(buffer, string, len);
	buffer[len] = 0;

	return buffer;
}

/*
 ==================
 Mem_TouchMemory
 ==================
*/
void Mem_TouchMemory (void){

	memoryBlock_t	*block;
	int				count, pagedIn = 0;
	int				i, time;

	time = Sys_Milliseconds();

	for (block = mem.blockChain.next; block != &mem.blockChain; block = block->next){
		// Check integrity
		if (block->prev->next != block || block->next->prev != block)
			Com_Error(true, "Mem_TouchMemory: memory block does not have proper link");

		if (block->id != BLOCK_ID)
			Com_Error(true, "Mem_TouchMemory: memory block has invalid id");

		if (*(int *)((byte *)block + sizeof(memoryBlock_t) + block->size) != BLOCK_ID)
			Com_Error(true, "Mem_TouchMemory: memory block wrote past end");

		// Make sure the block is paged in
		count = block->bytes >> 2;

		for (i = 0; i < count; i += 64)
			pagedIn += ((int *)block)[i];
	}

	Com_Printf("%.2f MB of memory touched in %i msec\n", mem.totalBytes * (1.0f/1048576), Sys_Milliseconds() - time);
}


// ============================================================================


/*
 ==================
 Mem_PrintStats
 ==================
*/
void Mem_PrintStats (void){

	if (mem_showMemoryUsage->integerValue){
		Com_Printf("total allocated memory: %6i, %6.2f MB\n", mem.totalBlocks, mem.totalBytes * (1.0f/1048576));
		Com_Printf("frame alloc: %4i, %4i KB    frame free: %4i, %4i KB\n", mem.frameAllocBlocks, mem.frameAllocBytes >> 10, mem.frameFreeBlocks, mem.frameFreeBytes >> 10);
	}

	// Clear for next frame
	mem.frameAllocBlocks = mem.frameAllocBytes = 0;
	mem.frameFreeBlocks = mem.frameFreeBytes = 0;
}

/*
 ==================
 Mem_Info_f
 ==================
*/
static void Mem_Info_f (void){

	memoryBlock_t	*block;
	int				clientBlocks = 0, clientBytes = 0;
	int				serverBlocks = 0, serverBytes = 0;
	int				commonBlocks = 0, commonBytes = 0;
	int				collisionBlocks = 0, collisionBytes = 0;
	int				rendererBlocks = 0, rendererBytes = 0;
	int				soundBlocks = 0, soundBytes = 0;
	int				guiBlocks = 0, guiBytes = 0;
	int				tempBlocks = 0, tempBytes = 0;
	int				totalOverhead = 0;

	for (block = mem.blockChain.next; block != &mem.blockChain; block = block->next){
		// Check integrity
		if (block->prev->next != block || block->next->prev != block)
			Com_Printf(S_COLOR_RED "ERROR: memory block does not have proper link");

		if (block->id != BLOCK_ID)
			Com_Printf(S_COLOR_RED "ERROR: memory block has invalid id");

		if (*(int *)((byte *)block + sizeof(memoryBlock_t) + block->size) != BLOCK_ID)
			Com_Printf(S_COLOR_RED "ERROR: memory block wrote past end");

		// Count per-tag usage
		switch (block->tag){
		case TAG_CLIENT:
			clientBlocks++;
			clientBytes += block->bytes;
			break;
		case TAG_SERVER:
			serverBlocks++;
			serverBytes += block->bytes;
			break;
		case TAG_COMMON:
			commonBlocks++;
			commonBytes += block->bytes;
			break;
		case TAG_COLLISION:
			collisionBlocks++;
			collisionBytes += block->bytes;
			break;
		case TAG_RENDERER:
			rendererBlocks++;
			rendererBytes += block->bytes;
			break;
		case TAG_SOUND:
			soundBlocks++;
			soundBytes += block->bytes;
			break;
		case TAG_GUI:
			guiBlocks++;
			guiBytes += block->bytes;
			break;
		case TAG_TEMP:
			tempBlocks++;
			tempBytes += block->bytes;
			break;
		}

		// Also count total overhead
		totalOverhead += (block->bytes - block->size);
	}

	// Print totals
	Com_Printf("\n");
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Client", clientBytes, clientBytes * (1.0f/1048576), clientBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Server", serverBytes, serverBytes * (1.0f/1048576), serverBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Common", commonBytes, commonBytes * (1.0f/1048576), commonBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Collision", collisionBytes, collisionBytes * (1.0f/1048576), collisionBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Renderer", rendererBytes, rendererBytes * (1.0f/1048576), rendererBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Sound", soundBytes, soundBytes * (1.0f/1048576), soundBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "GUI", guiBytes, guiBytes * (1.0f/1048576), guiBlocks);
	Com_Printf("%-10s: %9i bytes (%6.2f MB) in %6i blocks\n", "Temp", tempBytes, tempBytes * (1.0f/1048576), tempBlocks);
	Com_Printf("--------------------------------------------------------\n");
	Com_Printf("%i total memory blocks\n", mem.totalBlocks);
	Com_Printf("%.2f MB total memory allocated (%i KB total overhead)\n", mem.totalBytes * (1.0f/1048576), totalOverhead >> 10);
	Com_Printf("\n");
}

/*
 ==================
 Mem_Init
 ==================
*/
void Mem_Init (void){

	if (sizeof(memoryBlock_t) & 7)
		Com_Error(true, "Mem_Init: memory block header is not 8 byte aligned");

	mem.blockChain.prev = mem.blockChain.next = &mem.blockChain;

	// Register our variables and commands
	mem_showMemoryUsage = CVar_Get("mem_showMemoryUsage", "0", CVAR_CHEAT, "Show total and per-frame memory usage");

	Cmd_AddCommand("memoryInfo", Mem_Info_f, "Shows memory information");
}

/*
 ==================
 Mem_Shutdown
 ==================
*/
void Mem_Shutdown (void){

	memoryBlock_t	*block, *next;

	if (!mem.blockChain.next)
		return;

	Cmd_RemoveCommand("memoryInfo");

	// Free all the memory
	for (block = mem.blockChain.next; block != &mem.blockChain; block = next){
		next = block->next;

		Mem_Free((void *)((byte *)block + sizeof(memoryBlock_t)));
	}

	memset(&mem, 0, sizeof(memory_t));
}

/**
======================================
 NEW and DELETE override
 All we have to do is declare these operators and all new/delete calls will be redirected here
======================================
*/
void *operator new(size_t size,memoryTag_t tag){
	return Mem_ClearedAlloc(size,tag);
}

void operator delete(void *p,memoryTag_t tag){
	Mem_Free(p);
}
void operator delete[]( void * p , memoryTag_t){
    operator delete(p);
}
void *operator new(size_t size){
	return calloc(1,size);
}
void *operator new[](size_t size){
	return calloc(1,size);
}
//this gets called both for things allocated with new(tag) and new() so we have to check if
//pointer was allocated with Mem_Alloc.
void operator delete(void *p){
	memoryBlock_t	*block;
	block = (memoryBlock_t *)((byte *)p - sizeof(memoryBlock_t));
	if (block->id != BLOCK_ID)
		free(p);
	else Mem_Free(p);
}
void operator delete[]( void * p ){
    operator delete(p);
}